import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;
    public TcpEchoServer(int port) throws IOException{
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService service = Executors.newCachedThreadPool();
        while(true){
            //通过accep方法，把内核中已经建立好的连接拿到应用程序中
            //建立连接的细节刘晨都是内核自动完成的，应用程序只需要“捡现成的”
            Socket clientSocket = serverSocket.accept();
            //此处不应该直接调用processConnection（），会导致服务器不能处理多个客户端
            //创建新的线程来调用

            //这种方法可行，但不够好
            /*Thread t = new Thread(()->{
                processConnection(clientSocket);
            });
            t.start();*/

            //更好一点的方法是，使用线程池
            service.submit(new Runnable(){
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }
    public void processConnection(Socket clientSocket){
        System.out.printf("[%s:%d]客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
        //这里如果使用System.out.println,使用错误，System.out.println 方法用于简单的字符串输出
        // 不支持像 printf 那样的格式化字符串。你代码里使用了格式化字符串 [%s:%d]客户端上线\n
        // 但用的是 println 方法，这会导致无法正确格式化输出，而且会直接把格式化字符串原样输出
        try(  InputStream inputStream = clientSocket.getInputStream();
              OutputStream outputStream = clientSocket.getOutputStream();){
            //使用try（）方式,避免后续用到了流对象，忘记关闭
            //由于客户端发来的数据，可能是“多条数据”，针对多条数据，就循环的处理
            while(true){
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    //连接断开了，此时循环就应该结束
                    System.out.printf("[%s:%d]客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                //1.读取请求并解析，此处就以next来作为读取请求的方式，next的规则是，读到空白就返回
                String request = scanner.next();
                //2.根据请求，计算响应
                String response = process(request);
                //3.把响应写回到客户端
                //可以把String转成字节数组，写入到OutputStream
                //也可以使用PrintWriter把OutputStream包裹一下，来写入字符中
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                //此处的println就不是打印到控制台了，而是写入outputStream对应的流对象中，也就是clientSocket中
                //自然这个数据也就通过网络发送出去了（发给单签这个链接的另外一端）
                //此处使用println带有\n也是为了客户端使用scanner.next读取方便
                printWriter.flush();//刷新缓冲区，否则数据可能还在内存中，没有写入网卡
                System.out.printf("[%s:%d] req=%s,resp=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            try {
                //在这里进行clientSocket的关闭
                //在这里关闭是因为process在这里建立连接，这个方法执行完毕，这个连接也就处理完了
                //close加在方法末尾
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public String process(String request){
        //此处是回显服务器，响应和请求是一样的
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }



}
